Skip to content

tools(parity): stop the scan walking into build output - #2259

Merged
ryanbr merged 1 commit into
mainfrom
parity-glob-hardening
Sep 16, 2026
Merged

ryanbr merged 1 commit into
mainfrom
parity-glob-hardening

Conversation

@ryanbr

@ryanbr ryanbr commented Sep 16, 2026

Copy link
Copy Markdown
Owner

Every glob in the ledger ends in **, and ** descends into a build directory exactly
as happily as into a source tree. A SwiftPM dependency checked out under
Packages/StrandAnalytics/.build/checkouts/ lays itself out with a Sources directory,
by convention, and therefore matches Packages/**/Sources/**/*.swift as squarely as a
first-party file does.

On this machine that was 3,543 files out of 6,039 matched: 59 percent of what the
scanner read was somebody else's vendored library.

WHY IT IS WORSE THAN NOISE

The pollution is SUBTRACTIVE. It does not add findings a reader would question, it
REMOVES them, by handing a declaration a callsite that exists only in a vendored copy.

That is not hypothetical. A scan on a working tree came back one test-only-callsite
short for Packages/StrandAnalytics against a baseline derived on a clean checkout, and
the difference was GRDB's SPM smoke test at Tests/SPM/PlainPackage/Sources/SPM/main.swift
calling read, write and execute. The local acceptance test passed anyway, because both
sides of its comparison came from the same polluted tree. CI checks out clean, so it
never saw any of it and could not warn. The count went 44 to 43: a finding vanished
rather than a spurious one appearing, which is the direction nobody goes looking for.

THE CHANGE

One filter in _paths, which is the only place this module globs: the declaration scan,
the reference scan and both callsite corpora all come through it. Paths inside .build,
.swiftpm, DerivedData, build, .gradle, node_modules or Pods are dropped.

CI OUTPUT IS UNCHANGED BY CONSTRUCTION

CI already checks out clean, so it was always seeing the filtered numbers. This makes a
working tree agree with CI, not the other way round. Measured both ways on the same
commit:

polluted tree, filtered 266 / 234 / 1135 / 1353 / 2496
pristine checkout 266 / 234 / 1135 / 1353 / 2496

for SWIFT, KOTLIN, PRODUCTION, TEST and REFERENCE globs respectively.

THE INVARIANT IS TESTED, NOT ASSERTED

Five tests. The one that matters checks the safety property against git rather than
against my judgement: everything the filter drops is untracked, so no file the
repository actually contains can be hidden by it. Here that is 3,543 dropped and 0
tracked.

The others pin the exact shape that caused this (a dependency checkout whose own layout
puts Sources under .build), the second copy SwiftPM writes under index-build,
Gradle output for the day KSP emits Kotlin rather than Java, and a negative case: build
is matched as a whole directory component only, so Sources/buildsystem/build.swift
stays in the scan.

NOT IN THIS CHANGE

SWIFT_EXCLUDED_GLOBS and KOTLIN_EXCLUDED_GLOBS are still dead constants, referenced
nowhere but their own definitions. They document which packages are deliberately out of
scope and wiring them up would change what is scanned, so that belongs in its own
change with its own re-derive.

Governance suite 129 tests, 0 failures.

Every glob in the ledger ends in `**`, and `**` descends into a build directory exactly
as happily as into a source tree. A SwiftPM dependency checked out under
`Packages/StrandAnalytics/.build/checkouts/` lays itself out with a `Sources` directory,
by convention, and therefore matches `Packages/**/Sources/**/*.swift` as squarely as a
first-party file does.

On this machine that was 3,543 files out of 6,039 matched: 59 percent of what the
scanner read was somebody else's vendored library.

WHY IT IS WORSE THAN NOISE

The pollution is SUBTRACTIVE. It does not add findings a reader would question, it
REMOVES them, by handing a declaration a callsite that exists only in a vendored copy.

That is not hypothetical. A scan on a working tree came back one test-only-callsite
short for Packages/StrandAnalytics against a baseline derived on a clean checkout, and
the difference was GRDB's SPM smoke test at Tests/SPM/PlainPackage/Sources/SPM/main.swift
calling read, write and execute. The local acceptance test passed anyway, because both
sides of its comparison came from the same polluted tree. CI checks out clean, so it
never saw any of it and could not warn. The count went 44 to 43: a finding vanished
rather than a spurious one appearing, which is the direction nobody goes looking for.

THE CHANGE

One filter in `_paths`, which is the only place this module globs: the declaration scan,
the reference scan and both callsite corpora all come through it. Paths inside `.build`,
`.swiftpm`, `DerivedData`, `build`, `.gradle`, `node_modules` or `Pods` are dropped.

CI OUTPUT IS UNCHANGED BY CONSTRUCTION

CI already checks out clean, so it was always seeing the filtered numbers. This makes a
working tree agree with CI, not the other way round. Measured both ways on the same
commit:

  polluted tree, filtered   266 / 234 / 1135 / 1353 / 2496
  pristine checkout         266 / 234 / 1135 / 1353 / 2496

for SWIFT, KOTLIN, PRODUCTION, TEST and REFERENCE globs respectively.

THE INVARIANT IS TESTED, NOT ASSERTED

Five tests. The one that matters checks the safety property against git rather than
against my judgement: everything the filter drops is untracked, so no file the
repository actually contains can be hidden by it. Here that is 3,543 dropped and 0
tracked.

The others pin the exact shape that caused this (a dependency checkout whose own layout
puts `Sources` under `.build`), the second copy SwiftPM writes under `index-build`,
Gradle output for the day KSP emits Kotlin rather than Java, and a negative case: `build`
is matched as a whole directory component only, so `Sources/buildsystem/build.swift`
stays in the scan.

NOT IN THIS CHANGE

SWIFT_EXCLUDED_GLOBS and KOTLIN_EXCLUDED_GLOBS are still dead constants, referenced
nowhere but their own definitions. They document which packages are deliberately out of
scope and wiring them up would change what is scanned, so that belongs in its own
change with its own re-derive.

Governance suite 129 tests, 0 failures.
@ryanbr
ryanbr merged commit 97edae8 into main Sep 16, 2026
5 checks passed
@ryanbr
ryanbr deleted the parity-glob-hardening branch September 16, 2026 06:12
ryanbr added a commit that referenced this pull request Sep 19, 2026
…product commits

`parity_ratchet.py` has failed on clean main since the twin map was last written:

    ERROR: Tools/parity_twin_map.json: base authority cannot be reproduced with the
    current derivation; migration required (see --migrate-authority)

The message points at the derivation. Measured, the derivation is innocent: running
the CURRENT scanner against the tree the map was written from (b6e4e3e) reproduces
the stored manifest EXACTLY, +0 across all twelve sets. So the two scanner changes
that landed since (#2259 stopped the scan walking into build output, #2267 stopped
an unparseable argument erasing a callsite) alter what the scanner would find in
general, but change nothing this repository actually contains.

What moved is the TREE. Three in-scope commits landed after the map: #2322
(AlarmReadback), #2307 (the Oura auth handshake) and #2260 (the WHOOP sports).

Re-derived with the guarded workflow, `--refresh-derived --migrate-authority`, which
writes the candidates, re-runs the ratchet against them, and restores the originals
unless it accepts. Migration waives only the stored manifest's reproducibility, never
a semantic finding, so undeclared one-sided debt would still have failed here.

Every count now reconciles against those commits rather than being asserted:

  files            500 -> 502   the two new OuraAuthWatchdog files (#2307)
  file_pairs        66 -> 67    OuraDriver is now resolvable as a twin pair
  functions       4449 -> 4453  +1 pair (2) + 2 unpaired = 4
  function_pairs   173 -> 174
  unpaired_funcs  4109 -> 4111
  constants       1943 -> 1945  both unpaired
  unpaired_consts  591 -> 593
  unpaired_files   386 -> 386   count held, membership changed

That last one is the interesting row. OuraDriver.swift and OuraDriver.kt LEFT the
unpaired set (hence the extra file pair) while OuraAuthWatchdog.swift and
OuraAuthWatchdog.kt JOINED it: #2307 added a twin on both platforms without a
declared twin reference, so the ledger does not yet know the two mirror each other.
Not debt by the scanner's own reckoning, and left as-is here rather than bundled
into a re-derivation, but worth a look.

`parity_ledger_baseline.json` is untouched. The 300 known findings are identical, so
nothing was added or forgiven.

This is the drift mode parity-governance.yml's own header documents, including that
it has landed on an outside contributor's PR three times. The daily schedule on main
exists to catch it; this is the maintainer action it asks for.
ryanbr added a commit that referenced this pull request Sep 19, 2026
… commits (#2325)

chore(parity): re-derive the twin-map authority after three in-scope product commits

`parity_ratchet.py` has failed on clean main since the twin map was last written:

    ERROR: Tools/parity_twin_map.json: base authority cannot be reproduced with the
    current derivation; migration required (see --migrate-authority)

The message points at the derivation. Measured, the derivation is innocent: running
the CURRENT scanner against the tree the map was written from (b6e4e3e) reproduces
the stored manifest EXACTLY, +0 across all twelve sets. So the two scanner changes
that landed since (#2259 stopped the scan walking into build output, #2267 stopped
an unparseable argument erasing a callsite) alter what the scanner would find in
general, but change nothing this repository actually contains.

What moved is the TREE. Three in-scope commits landed after the map: #2322
(AlarmReadback), #2307 (the Oura auth handshake) and #2260 (the WHOOP sports).

Re-derived with the guarded workflow, `--refresh-derived --migrate-authority`, which
writes the candidates, re-runs the ratchet against them, and restores the originals
unless it accepts. Migration waives only the stored manifest's reproducibility, never
a semantic finding, so undeclared one-sided debt would still have failed here.

Every count now reconciles against those commits rather than being asserted:

  files            500 -> 502   the two new OuraAuthWatchdog files (#2307)
  file_pairs        66 -> 67    OuraDriver is now resolvable as a twin pair
  functions       4449 -> 4453  +1 pair (2) + 2 unpaired = 4
  function_pairs   173 -> 174
  unpaired_funcs  4109 -> 4111
  constants       1943 -> 1945  both unpaired
  unpaired_consts  591 -> 593
  unpaired_files   386 -> 386   count held, membership changed

That last one is the interesting row. OuraDriver.swift and OuraDriver.kt LEFT the
unpaired set (hence the extra file pair) while OuraAuthWatchdog.swift and
OuraAuthWatchdog.kt JOINED it: #2307 added a twin on both platforms without a
declared twin reference, so the ledger does not yet know the two mirror each other.
Not debt by the scanner's own reckoning, and left as-is here rather than bundled
into a re-derivation, but worth a look.

`parity_ledger_baseline.json` is untouched. The 300 known findings are identical, so
nothing was added or forgiven.

This is the drift mode parity-governance.yml's own header documents, including that
it has landed on an outside contributor's PR three times. The daily schedule on main
exists to catch it; this is the maintainer action it asks for.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant